/**
 * 主方法
 * @param cssSelect css选择器
 */
function dom2canvas(cssSelect) {
   var dom = document.querySelector(cssSelect);
   var canvas = createRightSizeCanvasDom(dom);
   var nodesCount = getNodesCount(dom);
   return new Promise(function (resolve) {
      drawingCanvasByDom(canvas, dom, nodesCount, resolve);
   })
}

/**
 * 获得node数量
 * @param dom 
 */
function getNodesCount(dom) {
   var countObj = {
      count: 0
   };
   getChildCount(dom.children, countObj);
   var count = countObj.count;
   countObj = null;
   return count;
}

/**
 * 递归获取child数量
 */
function getChildCount(nodes, countObj) {
   countObj.count += nodes.length;
   for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      if (node.children && node.children.length > 0) {
         getChildCount(node.children, countObj);
      }
   }
}

/**
 * 根据dom生成正确尺寸的canvas
 * @param dom 
 */
function createRightSizeCanvasDom(dom) {
   var width = dom.clientWidth * 2;
   var height = dom.clientHeight * 2;
   var canvas = document.createElement('canvas');
   canvas.width = width;
   canvas.height = height;
   return canvas;
}

/**
 * 根据dom绘制canvas
 * @param canvas 
 * @param dom 
 */
function drawingCanvasByDom(canvas, dom, nodesCount, resolve) {
   var ctx = canvas.getContext('2d');
   drawingBg(ctx, dom);
   // 循环遍历子节点绘制
   var countObj = {
      count: 0
   };
   loopChildren(ctx, dom, dom.children, drawingNode, countObj, nodesCount, canvas, resolve);
}

/**
 * 绘制背景
 * @param ctx 
 * @param node 
 */
function drawingBg(ctx, node) {
   var width = node.clientWidth * 2;
   var height = node.clientHeight * 2;
   var bgColor = getStyle(node).backgroundColor;
   ctx.fillStyle = bgColor;
   ctx.fillRect(0, 0, width, height);
}

/**
 * 获取样式
 * @param node 
 */
function getStyle(node) {
   var style = document.defaultView.getComputedStyle(node);
   return style;
}

/**
 * 递归遍历子节点
 * @param ctx 
 * @param nodes 
 * @param callback 
 */
function loopChildren(ctx, root, nodes, callback, countObj, nodesCount, canvas, resolve) {
   for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      callback(ctx, root, node, countObj, nodesCount, canvas, resolve);
      if (node.children && node.children.length > 0) {
         loopChildren(ctx, root, node.children, callback, countObj, nodesCount, canvas, resolve);
      }
   }
}

/**
 * 绘制节点
 * @param ctx 
 * @param node 
 */
function drawingNode(ctx, root, node, countObj, nodesCount, canvas, resolve) {
   var offset = {
      x: 0,
      y: 0
   };
   computeOffsetWithParent(offset, root, node);
   offset.x = offset.x * 2;
   offset.y = offset.y * 2;
   var style = getStyle(node);
   if (style.display == 'none') {
      return;
   }
   var matrix = style.transform;
   var angle = null;
   if (matrix && matrix != 'none') {
      angle = computeRotate(matrix) / (180 / Math.PI);
   }
   if (angle) {
      var width = node.clientWidth;
      var height = node.clientHeight;
      var rectCenterPoint = {
         x: offset.x + width,
         y: offset.y + height
      };
      ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
      ctx.rotate(angle);
      ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
   }
   var borderRadius = style.borderRadius.indexOf('%') > -1 ? parseFloat(style.borderRadius) * node.clientWidth / 100 : parseFloat(style.borderRadius);
   switch (node.nodeName) {
      case "IMG":
         var w = node.clientWidth * 2;
         var h = node.clientHeight * 2;
         var x = offset.x;
         var y = offset.y;
         if (node.complete) {
            drawImage(ctx, node, borderRadius, w, h, x, y);
            if (angle) {
               ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
               ctx.rotate(-angle);
               ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
            }
            endDraw(countObj, nodesCount, canvas, resolve)
         } else {
            node.onload = function () {
               drawImage(ctx, node, borderRadius, w, h, x, y);
               if (angle) {
                  ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
                  ctx.rotate(-angle);
                  ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
               }
               endDraw(countObj, nodesCount, canvas, resolve)
            }
         }
         break;
      case "SPAN":
         if (style.display != 'inline') {
            var width = node.clientWidth * 2;
            var height = node.clientHeight * 2;
            var bgColor = style.backgroundColor;
            ctx.fillStyle = bgColor;
            ctx.fillRect(offset.x, offset.y, width, height);
            var borderColor = style.borderBottomColor;
            var borderWidth = style.borderWidth;
            if (parseFloat(borderWidth)) {
               ctx.strokeStyle = borderColor;
               ctx.lineWidth = borderWidth;
               ctx.strokeRect(offset.x, offset.y, width, height);
            }
         }
         if (node.innerText) {
            var fontStyle = style.fontStyle;
            var fontSize = style.fontSize;
            var fontWeight = style.fontWeight;
            var fontFamily = style.fontFamily;
            var color = style.color;
            var orilineHeight = style.lineHeight && style.lineHeight != 'normal' && parseFloat(style.lineHeight) * 2;
            var font = (parseFloat(fontSize) * 2).toFixed(2) + 'px';
            ctx.font = fontStyle + ' ' + fontWeight + ' ' + font + ' ' + fontFamily;
            ctx.fillStyle = color;
            var maxWidth = parseInt(node.clientWidth * 2);
            let arrText = node.innerText.split('');
            let line = '';
            var x = offset.x;
            var textAlign = style.textAlign;
            if (textAlign == 'center') {
               ctx.textAlign = 'center';
               x = offset.x + node.clientWidth;
            }
            if (angle) {
               x = offset.x + node.clientWidth * 1.8;
            }
            var y = offset.y + (parseFloat(fontSize) * 1) + 10;
            var lineHiehgtOffset = orilineHeight ? (orilineHeight - parseFloat(fontSize) * 2) : 0;
            var lineHeight = parseFloat(fontSize) * 2.16 + lineHiehgtOffset;
            for (let n = 0; n < arrText.length; n++) {
               let testLine = line + arrText[n];
               let metrics = ctx.measureText(testLine);
               let testWidth = metrics.width;
               if (testWidth > maxWidth && n > 0) {
                  ctx.fillText(line, x, y);
                  line = arrText[n];
                  y += lineHeight;
               } else {
                  line = testLine;
               }
            }
            ctx.fillText(line, x, y);
         }
         if (angle) {
            ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
            ctx.rotate(-angle);
            ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
         }
         endDraw(countObj, nodesCount, canvas, resolve)
         break;
      default:
         var width = node.clientWidth * 2;
         var height = node.clientHeight * 2;
         var bgColor = style.backgroundColor;
         ctx.fillStyle = bgColor;
         ctx.fillRect(offset.x, offset.y, width, height);
         var borderColor = style.borderBottomColor;
         var borderWidth = style.borderWidth;
         if (parseFloat(borderWidth)) {
            ctx.strokeStyle = borderColor;
            ctx.lineWidth = borderWidth;
            ctx.strokeRect(offset.x, offset.y, width, height);
         }
         if (angle) {
            ctx.translate(rectCenterPoint.x, rectCenterPoint.y);
            ctx.rotate(-angle);
            ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y);
         }
         endDraw(countObj, nodesCount, canvas, resolve)
   }
}

/**
 * 结束绘制
 * @param countObj 
 * @param nodesCount 
 * @param canvas 
 * @param resolve 
 */
function endDraw(countObj, nodesCount, canvas, resolve) {
   countObj.count++;
   if (countObj.count == nodesCount) {
      resolve(canvas)
   }
}

/**
 * 绘制图片带圆角
 */
function drawImage(ctx, node, borderRadius, w, h, x, y) {
   if (borderRadius) {
      var r = borderRadius * 2;
      if (w < 2 * r) {
         r = w / 2;
      }
      if (h < 2 * r) {
         r = h / 2;
      }
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + w, y, x + w, y + h, r);
      ctx.arcTo(x + w, y + h, x, y + h, r);
      ctx.arcTo(x, y + h, x, y, r);
      ctx.arcTo(x, y, x + w, y, r);
      ctx.closePath();
      ctx.clip();
      ctx.drawImage(node, x, y, w, h);
      ctx.restore();
   } else {
      ctx.drawImage(node, x, y, w, h);
   }
}

function computeRotate(matrix) {
   var values = matrix.split('(')[1].split(')')[0].split(',');
   var a = values[0];
   var b = values[1];
   var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
   return angle;
}

/**
 * 向上遍历获取坐标
 * @param offset 
 * @param node 
 */
function computeOffsetWithParent(offset, root, node) {
   if (node === root) {
      return
   }
   var left = node.offsetLeft;
   var top = node.offsetTop;
   left && (offset.x += left);
   top && (offset.y += top);
   node.offsetParent && computeOffsetWithParent(offset, root, node.offsetParent);
}